﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public enum BunkerHitPointType
{
    Metal = 1,
    Wall = 2,
    PaperBox = 3,
    Wood = 4,
    MetalBack = 11,
    WallBack = 12,
    PaperBoxBack = 13,
    WoodBack = 14,
}

public class BunkerHitPoint : MonoBehaviour
{
    public List<BunkerSingleHitPoint> singlePoints = new List<BunkerSingleHitPoint>();
    public List<BunkerHitPointArea> areas = new List<BunkerHitPointArea>();
    public List<BunkerHitPointCollider> colliders = new List<BunkerHitPointCollider>();
    
    public static void AutoFix(BunkerHitPointArea point)
    {
        AutoFix(point.transform,point.positions,point.directions);
    }

    public static Vector3 FindNearestPointOnMesh(Vector3 point,Transform root)
    {
        Mesh targetMesh = null;
        Transform objTrans = null;
        var meshFilters = root.GetComponentsInChildren<MeshFilter>();
        var closestPoint = new Vector3(9999, 9999, 9999);
        bool find = false;
        for (int i = 0; i < meshFilters.Length; i++)
        {
            var meshFilter = meshFilters[i];
            //检查下有没有renderer，没有就跳过
            var meshRenderer = meshFilter.gameObject.GetComponent<MeshRenderer>();
            if (meshRenderer == null || meshRenderer.enabled == false)
            {
                continue;
            }
            targetMesh = meshFilter.sharedMesh;
            objTrans = meshFilter.gameObject.transform;
            if (targetMesh != null)
            {
                var position = point;
                var direction = Vector3.up;
                GetNearestPositionAndNormal(targetMesh, objTrans, ref position, ref direction);
                if (Vector3.Distance(closestPoint, point) > Vector3.Distance(position, point))
                {
                    find = true;
                    closestPoint = position;
                }
            }
        }

        if (find)
        {
            return closestPoint;
        }
        return closestPoint;
    }
    
    public static void AutoFix(Transform point,List<Vector3> positions = null,List<Vector3> directions = null)
    {
        var parent = point;
        Mesh targetMesh = null;
        var objName = string.Empty;
        Transform objTrans = null;
        while (true)
        {
            var meshFilter = parent.GetComponentInChildren<MeshFilter>();
            if (meshFilter == null)
            {
                parent = parent.parent;
                if (parent == null)
                {
                    break;
                }
            }
            else
            {
                targetMesh = meshFilter.sharedMesh;
                objName = meshFilter.gameObject.name;
                objTrans = meshFilter.gameObject.transform;
                break;
            }
        }

        if (targetMesh != null)
        {
            if (positions == null || directions == null)
            {
                var position = point.position;
                var direction = point.forward;
                GetNearestPositionAndNormal(targetMesh, objTrans, ref position, ref direction);
                point.position = position;
                point.forward = direction;
            }
            else
            {
                for (int i = 0; i < positions.Count; i++)
                {
                    var position = positions[i];
                    var direction = directions[i];
                    GetNearestPositionAndNormal(targetMesh, objTrans, ref position, ref direction);
                    positions[i] = position;
                    directions[i] = direction;
                }
            }
           
        }
    }

    public static void GetNearestPositionAndNormal(Mesh targetMesh,Transform objTrans, ref Vector3 position, ref Vector3 direction)
    {
        float distance = 999;
        Vector3 candidatePosition = position;
        Vector3 candidateNormal = direction;
        
        List<Vector3> vertices = new List<Vector3>();
        targetMesh.GetVertices(vertices);
        for (int i = 0; i < targetMesh.triangles.Length; i+= 3)
        {
            var v1 = targetMesh.triangles[i];
            var v2 = targetMesh.triangles[i + 1];
            var v3 = targetMesh.triangles[i + 2];
            var p1 = objTrans.TransformPoint(vertices[v1]);
            var p2 = objTrans.TransformPoint(vertices[v2]);
            var p3 = objTrans.TransformPoint(vertices[v3]);
            var plane = new Plane(p1, p2, p3);
            var candidate = plane.ClosestPointOnPlane(position);
            if (PointInTriangle(p1, p2, p3, candidate))
            {
                //点再三角形内，不用处理
            }
            else if(candidate == position)
            {
                continue;
            }
            else
            {
                //点再三角形外，则最近点要么再顶点上，要么再垂足上
                //先求出垂足
                List<Vector3> l = new List<Vector3>();
                var pos = ClosestPointOnLine(p1,p2,candidate);
                if (pos != Vector3.zero)
                {
                    l.Add(pos);
                }
                pos = ClosestPointOnLine(p2,p3,candidate);
                if (pos != Vector3.zero)
                {
                    l.Add(pos);
                }
                pos = ClosestPointOnLine(p3,p1,candidate);
                if (pos != Vector3.zero)
                {
                    l.Add(pos);
                }
                //然后加上三顶点
                l.Add(p1);
                l.Add(p2);
                l.Add(p3);
                float dd = 999;
                for (int j = 0; j < l.Count; j++)
                {
                    var ddd = Vector3.Distance(l[j], position);
                    if (ddd < dd)
                    {
                        dd = ddd;
                        candidate = l[j];
                    }
                }
            }
            var d = Vector3.Distance(candidate, position);
            if (d < distance)
            {
                distance = d;
                candidatePosition = candidate;
                candidateNormal = plane.normal;
            }
        }

        position = candidatePosition;
        direction = candidateNormal;
    }
    

    private static Vector3 ClosestPointOnLine(Vector3 a,Vector3 b,Vector3 p)
    {
        var dot = Vector3.Dot(p - a, b - a);
        var abDistance = Vector3.Distance(a, b);
        if (dot > abDistance || dot < 0)
        {
            //点再线段外
            return Vector3.zero;
        }
        return a + (b - a).normalized * dot;
    }
    
    //共面
    private static bool SamePlane(Vector3 A, Vector3 B, Vector3 C, Vector3 P)
    {
        Vector3 AB = B - A;
        Vector3 AC = C - A;
        Vector3 AP = P - A;

        Vector3 v1 = Vector3.Cross(AB, AC);
        Vector3 v2 = Vector3.Cross(AB, AP);

        // v1 and v2 should point to the same direction
        return Vector3.Dot(v1, v2) >= 0;
    }

    private static bool PointInTriangle(Vector3 A, Vector3 B, Vector3 C, Vector3 P)
    {
        var x = P.x;
        var y = P.y;
        var z = P.z;
        var maxX = Mathf.Max(Mathf.Max(A.x, B.x), C.x);
        var maxY = Mathf.Max(Mathf.Max(A.y, B.y), C.y);
        var maxZ = Mathf.Max(Mathf.Max(A.z, B.z), C.z);
        var minX = Mathf.Min(Mathf.Min(A.x, B.x), C.x);
        var minY = Mathf.Min(Mathf.Min(A.y, B.y), C.y);
        var minZ = Mathf.Min(Mathf.Min(A.z, B.z), C.z);
        if (x > maxX || x < minX || y > maxY || y < minY || z > maxZ || z < minZ)
        {
            return false;
        }
        return SamePlane(A, B, C, P);
    }
}
